
var identity_matr = mat4.create();
mat4.identity(identity_matr);

var g_num_nodes_drawn = 0;

var BVHNode = ( function() {

    function BVHNode (min_x, min_y, min_z, max_x, max_y, max_z, id) {
        "use strict";
        var mesh_ = null,

            diff_tex_ = null,
            light_tex_ = null,

            bbox_min = [min_x, min_y, min_z],
            bbox_max = [max_x, max_y, max_z],

            vertices_,

            id_ = id,
            child_ids_ = [],

            pvs_ = [],

            binary_ = false,
            div_plane_,
            div_val_;

        vertices_ =     [[min_x, min_y, min_z], [max_x, min_y, min_z], [max_x, min_y, max_z], [min_x, min_y, max_z],
                        [min_x, max_y, min_z], [max_x, max_y, min_z], [max_x, max_y, max_z], [min_x, max_y, max_z]];
        
        if (id_ == 1)
        alert(vertices_);

        this.id = function (){
            return id_;
        };

        this.pvs = function (){
            return pvs_;
        };

        this.bbox_min = function () {
            return bbox_min;
        };

        this.bbox_max = function () {
            return bbox_max;
        };

        this.set_mesh = function (mesh) {
            mesh_ = mesh;
        };

        this.set_diff_tex = function (tex) {
            diff_tex_ = tex;
        };

        this.set_light_tex = function (tex) {
            light_tex_ = tex;
        };




        this.AddVisible = function (id) {
            pvs_.push(id);
        };
        
        this.AddChild = function (id) {
            child_ids_.push(id);
        };

        this.Finish = function (nodes) {
            
            if (child_ids_.length == 2) 
            {
                binary_ = true;

                var dx = nodes[child_ids_[1]].bbox_min()[0] - nodes[child_ids_[0]].bbox_min()[0];
                var dy = nodes[child_ids_[1]].bbox_min()[1] - nodes[child_ids_[0]].bbox_min()[1];
                var dz = nodes[child_ids_[1]].bbox_min()[2] - nodes[child_ids_[0]].bbox_min()[2];

                if( (Math.abs(dx) >= Math.abs(dy)) && (Math.abs(dx) >= Math.abs(dz)) )
                {
                    div_plane_ = 0;
                    if(dx < 0)
                    {
                        var temp = child_ids_[0];
                        child_ids_[0] = child_ids_[1];
                        child_ids_[1] = temp;
                    }
                    div_val_ = nodes[child_ids_[1]].bbox_min()[0];
                } else

                if( (Math.abs(dy) >= Math.abs(dx)) && (Math.abs(dy) >= Math.abs(dz)) )
                {
                    div_plane_ = 1;
                    if(dy < 0)
                    {
                        var temp = child_ids_[0];
                        child_ids_[0] = child_ids_[1];
                        child_ids_[1] = temp;
                    }
                    div_val_ = nodes[child_ids_[1]].bbox_min()[1];
                } else

                if( (Math.abs(dz) >= Math.abs(dy)) && (Math.abs(dz) >= Math.abs(dx)) )
                {
                    div_plane_ = 2;
                    if(dz < 0)
                    {
                        var temp = child_ids_[0];
                        child_ids_[0] = child_ids_[1];
                        child_ids_[1] = temp;
                    }
                    div_val_ = nodes[child_ids_[1]].bbox_min()[2];
                }


            }

        };
        
        this.Traverse = function (point, nodes) {


            if (!binary_)
            {
                if (!this.TestPoint(point))
                {
                    return -1;
                } else
                {
                    if (mesh_ !== null)
                    {
                        return id_;
                    }
                }
            } else
            {
                if (mesh_ !== null)
                {
                    return id_;
                }
                if (point[div_plane_] > div_val_)
                {
                    return nodes[child_ids_[1]].Traverse(point, nodes);
                } else
                {
                    return nodes[child_ids_[0]].Traverse(point, nodes);
                }
            }

            for (var i = child_ids_.length - 1; i > 0; i--) 
            {
                var return_value = nodes[child_ids_[i]].Traverse(point, nodes);
                if(return_value != -1)
                    return return_value;
            }
            return nodes[child_ids_[0]].id();
        };

        this.ClosestChild = function (point, nodes) {

            if (mesh_ !== null)
            {
                return id_;
            }

            var clos = child_ids_[0];
            var min_dist_sqr = Infinity;

            for (var i = child_ids_.length - 1; i > 0; i--) 
            {
                var d = nodes[child_ids_[i]].DistSqr(point);
                if (d < min_dist_sqr)
                {
                    min_dist_sqr = d;
                    clos = child_ids_[i];
                }
            }

            //alert(child_ids_[clos]);
            return nodes[child_ids_[clos]].ClosestChild(point, nodes);
            //return clos;

        };
        
        this.TestPoint = function(point){
            
            if( (point[0] < bbox_max[0])&&
                (point[1] < bbox_max[1])&&
                (point[2] < bbox_max[2])&&
                (point[0] > bbox_min[0])&&
                (point[1] > bbox_min[1])&&
                (point[2] > bbox_min[2]) )
            {
                return true;
            } else
            {
                return false;
            }
                
        };

        this.DistSqr = function (point) {

            var dist_sqr = 0.0;
            if (point[0] < bbox_min[0])
                dist_sqr += (bbox_min[0] - point[0]) * (bbox_min[0] - point[0]);
            else if (point[0] > bbox_max[0])
                dist_sqr += (point[0] - bbox_max[0]) * (point[0] - bbox_min[0]);

            if (point[1] < bbox_min[1])
                dist_sqr += (bbox_min[1] - point[1]) * (bbox_min[1] - point[1]);
            else if (point[1] > bbox_max[1])
                dist_sqr += (point[1] - bbox_max[1]) * (point[1] - bbox_min[1]);

            if (point[2] < bbox_min[2])
                dist_sqr += (bbox_min[2] - point[2]) * (bbox_min[2] - point[2]);
            else if (point[2] > bbox_max[2])
                dist_sqr += (point[2] - bbox_max[2]) * (point[2] - bbox_min[2]);

            return dist_sqr;
        };
        
        this.Draw = function(){
            
            if(mesh_ !== null)
            {
                if(!g_render_state.current_cam.IsInFrustum(vertices_))
                {
                    //if(g_render_state.current_cam == main_cam)
                    //console.log("aaa");
                    return;
                }

                if(g_render_state.current_program.hasOwnProperty('uMVPMatrix'))
                    gl.uniformMatrix4fv(g_render_state.current_program.uMVPMatrix, false, g_render_state.current_cam.view_proj_matrix());
                if(g_render_state.current_program.hasOwnProperty('uMVMatrix'))
                    gl.uniformMatrix4fv(g_render_state.current_program.uMVMatrix, false, g_render_state.current_cam.view_matrix());
                if(g_render_state.current_program.hasOwnProperty('uMMatrix'))
                    gl.uniformMatrix4fv(g_render_state.current_program.uMMatrix, false, identity_matr);
                    
                if(g_render_state.current_program.hasOwnProperty('uSunPMatrix'))
                    gl.uniformMatrix4fv(g_render_state.current_program.uSunPMatrix, false, g_renderer.global_shadow_cam().view_proj_matrix());
                
                g_materials['world'].Use();
                
                g_renderer.BindTexture(DIFFUSEMAP_SLOT, diff_tex_);
                //g_renderer.BindTexture(LIGHTMAP_SLOT, light_tex_);
                mesh_.Draw();

                if(g_render_state.current_pass == BEAUTY_PASS)
                    g_num_nodes_drawn++;
            }
            
        };

        this.DrawPVS = function(nodes){
            this.Draw();

            for (var i = 0; i < pvs_.length; i++) {
                nodes[pvs_[i]].Draw();
            }
        };
    
    };
    
    return BVHNode;

})();

var BVHTree = (function(){
    
    function BVHTree(file_name){
        "use strict";
        var nodes_ = [];

        var lines = GetLines(file_name);
        var line = 0;

        var textures = [];
        //alert(lines[1]);
        while (line < lines.length)
        {
            if (lines[line].indexOf("Textures") != -1)
            {
                while (lines[line].indexOf("{") == -1)
                {
                    line++;
                }
                line++;
                while (lines[line].indexOf("}") == -1)
                {
                    var str = lines[line].match(/[\/.\w]+/g);
                    textures.push(g_renderer.LoadTexture(str[0]));
                    line++;
                }
            }else
            if (lines[line].indexOf("Node") != -1)
            {
                while (lines[line].indexOf("{") == -1)
                {
                    line++;
                }

                var node = {};

                while (lines[line].indexOf("}") == -1)
                {
                    if (lines[line].indexOf("bbox") != -1)
                    {
                        var str = lines[line].match(/[\/.\-\w]+/g);
                        //alert(parseFloat(str[1]));

                        node = new BVHNode( parseFloat(str[1]), parseFloat(str[2]), parseFloat(str[3]),
                                            parseFloat(str[4]), parseFloat(str[5]), parseFloat(str[6]),
                                            nodes_.length );
                        
                    }else
                    if (lines[line].indexOf("mesh") != -1)
                    {
                        var str = lines[line].match(/[\/.\w]+/g);
                        //alert(str[1]);
                        var mesh = new Mesh(str[1]);
                        node.set_mesh(mesh);
                    }else
                    if (lines[line].indexOf("pvs") != -1)
                    {
                        var str = lines[line].match(/\w+/g);
                        var num_vis = str.length - 1;
                        for (var i = 0; i < num_vis; i++) {
                            node.AddVisible(parseInt(str[1 + i]), 10);
                            //alert(parseInt(str[1 + i]));
                        }
                    }else
                    if (lines[line].indexOf("childs") != -1)
                    {
                        var str = lines[line].match(/\w+/g);
                        if (str[1] != "null")
                        {
                            for (var i = str.length - 1; i > 0; i--) {
                                node.AddChild(parseInt(str[i]), 10);
                                //alert(parseInt(str[i]));
                            }
                        }
                    }else
                    if (lines[line].indexOf("diff_texture") != -1)
                    {
                        var str = lines[line].match(/\w+/g);
                        node.set_diff_tex(textures[parseInt(str[1])]);
                    }else
                    if (lines[line].indexOf("light_texture") != -1)
                    {
                        var str = lines[line].match(/\w+/g);
                        node.set_light_tex(textures[parseInt(str[1])]);
                    }
                    line++;
                }
                nodes_.push(node);
            }
            line++;
        }

        for (var i = nodes_.length - 1; i >= 0; i--) {
            nodes_[i].Finish(nodes_);
        };

        lines = null;
        textures = null;

        this.Draw = function() {

            //console.time("Traverse");
            var node_index = nodes_[0].Traverse(g_render_state.current_cam.world_position(), nodes_);
            //console.timeEnd("Traverse");
            //alert(node_index);
            if(node_index === -1)
            {
                node_index = nodes_[0].ClosestChild(g_render_state.current_cam.world_position(), nodes_);
            }

            //console.log(node_index);

            g_render_state.current_cam.UpdatePlanes();

            g_num_nodes_drawn = 0;
            nodes_[node_index].DrawPVS(nodes_);

            if(g_render_state.current_pass == BEAUTY_PASS)
                console.log("nodes drawn: " + g_num_nodes_drawn);

        };

    };
    
    return BVHTree;
})();